package com.yuexi.app.bill;

import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.yuexi.app.coverter.Converter;
import com.yuexi.bill.api.BillService;
import com.yuexi.bill.cqe.BillQuery;
import com.yuexi.bill.cqe.PayBillCmd;
import com.yuexi.bill.cqe.PayBillDetailCmd;
import com.yuexi.bill.dto.BillDTO;
import com.yuexi.domain.bill.BillDomain;
import com.yuexi.domain.bill.bo.AddBillBO;
import com.yuexi.domain.bill.bo.AddBillDetailBO;
import com.yuexi.domain.bill.bo.BillBO;
import com.yuexi.domain.bill.bo.BillQueryBO;
import com.yuexi.domain.bill.definds.PayByEnum;
import com.yuexi.domain.bill.definds.PayTypeEnum;
import com.yuexi.domain.exception.ExceptionEnum;
import com.yuexi.domain.payment.PayDomain;
import com.yuexi.domain.payment.alipay.AliPayDomain;
import com.yuexi.domain.payment.alipay.bo.AliPayBO;
import com.yuexi.domain.payment.wxpay.WePayDomain;
import com.yuexi.domain.payment.wxpay.bo.MicropayBO;
import com.yuexi.domain.payment.wxpay.definds.CreateOrderErrorCode;
import com.yuexi.domain.syscode.SysCodeDomain;
import com.yuexi.domain.syscode.bo.SysCodeResultBO;
import com.yuexi.domain.table.TableDomain;
import com.yuexi.domain.table.bo.TableBO;
import com.yuexi.domain.util.StringConvertUtil;
import com.zenx.dto.TitleValueDTO;
import com.zenx.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author jie
 * @date 2023/4/6 -16:07
 */
@Slf4j
@Service
public class BillServiceImpl implements BillService {

    @Autowired
    private BillDomain domain;
    @Autowired
    private WePayDomain wePayDomain;
    @Autowired
    private AliPayDomain aliPayDomain;
    @Autowired
    private PayDomain payDomain;
    @Autowired
    private TableDomain tableDomain;
    @Autowired
    private SysCodeDomain sysCodeDomain;

    @Override
    public List<BillDTO> list(BillQuery query) {
        List<BillDTO> resultList = new ArrayList<>();
        BillQueryBO billQueryBO = Converter.INSTANCE.toBillQueryBO(query);
        List<BillBO> billBOList = domain.list(billQueryBO);

        // 获取所有区域类型
        List<SysCodeResultBO> areaTypeList = sysCodeDomain.list("AREA_TYPE");

        for (BillBO billBO : billBOList) {
            BillDTO billDTO = Converter.INSTANCE.toBillDTO(billBO);
            billDTO.setAreaType(toAreaTypeList(billBO.getAreaType(), areaTypeList));
            resultList.add(billDTO);
        }
        return resultList;
    }

    private List<TitleValueDTO> toAreaTypeList(String areaType, List<SysCodeResultBO> areaTypeList) {
        List<TitleValueDTO> titleValueDTOList = new ArrayList<>();

        List<String> areaTypes = StringConvertUtil.toList(areaType);
        for (String type : areaTypes) {
            for (SysCodeResultBO sysCodeResultBO : areaTypeList) {
                if (type.equals(sysCodeResultBO.getSubCode())) {
                    TitleValueDTO titleValueDTO = new TitleValueDTO();
                    titleValueDTO.setTitle(sysCodeResultBO.getNameCn());
                    titleValueDTO.setValue(type);
                    titleValueDTOList.add(titleValueDTO);
                    break;
                }
            }
        }

        return titleValueDTOList;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void payBill(PayBillCmd cmd) {
        // 检查结算参数
        this.checkPayBillParam(cmd);

        // 如果是现金支付
        if (cmd.getPayBy().equals(PayTypeEnum.CASH.name())) {
            // 保存账单信息
            BillBO billBO = this.saveBill(null, cmd);
            // 保存账单详情
            this.saveBillDetail(billBO.getId(), cmd);
        } else {
            // 如果是微信支付，实付取应收字段
            cmd.setActuallyPaid(cmd.getReceivable());

            // 根据authCode判断支付方式
            String payBy = payDomain.getPayBy(cmd.getAuthCode());
            if (payBy.equals(PayByEnum.WEPAY.name())) {
                wepay(cmd);
            } else if (payBy.equals(PayByEnum.ALIPAY.name())) {
                alipay(cmd);
            } else {
                throw new BusinessException(ExceptionEnum.UNSUPPORTED_PAY_BY);
            }
        }
    }

    /**
     * 检查结算参数
     */
    private void checkPayBillParam(PayBillCmd cmd) {
        Assert.isTrue(PayTypeEnum.contains(cmd.getPayBy()), "不支持的支付方式");

        if (PayTypeEnum.CODE.name().equals(cmd.getPayBy())) {
            Assert.notNull(payDomain.getPayBy(cmd.getAuthCode()), "付款码不正确");
        } else {
            Assert.isTrue(cmd.getActuallyPaid().compareTo(cmd.getReceivable()) >= 0, "收款金额不能小于应收金额");
        }

        List<String> tableIds = cmd.getDetails().stream().map(PayBillDetailCmd::getTableId)
                .collect(Collectors.toList());
        List<TableBO> tableBOList = tableDomain.getByIds(tableIds);
        Assert.isTrue(tableBOList.size() == tableIds.size(), "参数错误（桌台号）");
    }

    /**
     * 使用微信付款码支付
     */
    private void wepay(PayBillCmd cmd) {
        String outTradeNo = domain.generateOutTradeNo();
        MicropayBO bo = new MicropayBO();
        bo.setAuthCode(cmd.getAuthCode());
        bo.setOutTradeNo(outTradeNo);
        bo.setTotalFee(cmd.getActuallyPaid().multiply(BigDecimal.valueOf(100)).intValue());

        try {
            // 保存账单信息
            BillBO billBO = this.saveBill(outTradeNo, cmd);
            // 保存账单详情
            this.saveBillDetail(billBO.getId(), cmd);

            // 创建订单
            WxPayMicropayResult payResult = wePayDomain.micropay(bo);
            // 支付成功
            if ("SUCCESS".equals(payResult.getReturnCode())
                    && "SUCCESS".equals(payResult.getResultCode())
                    && "MICROPAY".equals(payResult.getTradeType())) {
                log.info("支付成功：outTradeNo: " + outTradeNo);
                return;
            } else {
                throw new RuntimeException(payResult.getReturnMsg());
            }

        } catch (WxPayException e) {
            if ("FAIL".equals(e.getReturnCode())) {
                throw new RuntimeException(e.getReturnMsg());
            }

            // 如果是未知状态
            if (CreateOrderErrorCode.isUnknownState(e.getErrCode())) {
                // 失败尝试次数
                int attemptNumber = 6;
                boolean success = wePayDomain.attemptQueryOrder(outTradeNo, attemptNumber);
                if (!success) {
                    wePayDomain.doReverseTry(outTradeNo);
                    throw new BusinessException(ExceptionEnum.ORDER_CANCELED);
                }

            } else {
                throw new RuntimeException(e.getErrCodeDes());
            }
        }
    }

    /**
     * 支付宝支付
     */
    private void alipay(PayBillCmd cmd) {
        String outTradeNo = domain.generateOutTradeNo();
        String amount = String.valueOf(cmd.getActuallyPaid());

        // 保存账单信息
        BillBO billBO = this.saveBill(outTradeNo, cmd);
        // 保存账单详情
        this.saveBillDetail(billBO.getId(), cmd);

        AliPayBO payBO = new AliPayBO();
        payBO.setAuthCode(cmd.getAuthCode());
        payBO.setOutTradeNo(outTradeNo);
        payBO.setTotalAmount(amount);
        // 支付宝下单
        aliPayDomain.payTry(payBO);

        // 查询订单
        int attemptNumber = 10;
        boolean querySuccess = aliPayDomain.attemptQueryOrder(outTradeNo, attemptNumber);
        if (!querySuccess) {
            // 撤单
            aliPayDomain.cancelTry(outTradeNo);
            throw new BusinessException(ExceptionEnum.ORDER_CANCELED);
        }

    }

    /**
     * 保存账单信息
     */
    private BillBO saveBill(String outTradeNo, PayBillCmd cmd) {
        //  写入账单信息、账单详情
        AddBillBO addBillBO = new AddBillBO();
        addBillBO.setOutTradeNo(outTradeNo);

        if (PayTypeEnum.CODE.name().equals(cmd.getPayBy())) {
            addBillBO.setPayBy(payDomain.getPayBy(cmd.getAuthCode()));
        } else {
            addBillBO.setPayBy(PayByEnum.CASH.name());
        }

        addBillBO.setCreateUser(cmd.getUserId());

        List<PayBillDetailCmd> details = cmd.getDetails();
        List<String> tableIds = new ArrayList<>();
        for (PayBillDetailCmd detail : details) {
            tableIds.add(detail.getTableId());
        }

        addBillBO.setAreaType(StringConvertUtil.toString(tableDomain.getAreaType(tableIds)));
        addBillBO.setOriginalPrice(cmd.getOriginalPrice());
        addBillBO.setReceivable(cmd.getReceivable());
        addBillBO.setActuallyPaid(cmd.getActuallyPaid());
        addBillBO.setGiveChange(cmd.getGiveChange());
        addBillBO.setDiscount(cmd.getDiscount());

        return domain.add(addBillBO);
    }

    /**
     * 保存账单详情
     */
    private void saveBillDetail(String billId, PayBillCmd cmd) {
        for (PayBillDetailCmd detail : cmd.getDetails()) {
            AddBillDetailBO addBO = new AddBillDetailBO();
            addBO.setBillId(billId);
            addBO.setTableId(detail.getTableId());
            addBO.setAmount(detail.getAmount());
            domain.addDetail(addBO);
        }
    }
}
